package net.peelmeagrape.hibernate.collections;

import net.peelmeagrape.hibernate.xmlgen.XmlAttribute;
import net.peelmeagrape.hibernate.xmlgen.XmlElement;
import net.peelmeagrape.hibernate.Column;

import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * <p>Appears anywhere the parent mapping element defines a join to a new table, and defines
 * the foreign key in the joined table, that references the primary key of the original table.
 * </p>
 * <p>Collection instances are distinguished in the database by the foreign key of the entity that
 * owns the collection.
 * This foreign key is referred to as the collection key column (or columns) of the collection table.
 * The collection key column is mapped by the &lt;key> element.
 * </p>
 * <p>There may be a nullability constraint on the foreign key column. For most collections, this is implied.
 * The not-null and update attributes are useful when mapping a unidirectional one to many association.
 * If you map a unidirectional one to many to a non-nullable foreign key, you must declare the key column
 * using not-null="true".
 * </p>
 * <p>
 * The foreign key constraint may use ON DELETE CASCADE.
 * We recommend that for systems where delete performance is important,
 * all keys should be defined on-delete="cascade", and Hibernate will use a
 * database-level ON CASCADE DELETE constraint, instead of many individual DELETE statements.
 * Be aware that this feature bypasses Hibernate's usual optimistic locking strategy for versioned data.
 * </p>
 * <p>
 * The {@link #notNull()} and {@link #update()} attributes are useful when mapping a
 * unidirectional one to many association. If you map a unidirectional one to many
 * to a non-nullable foreign key, you must declare the key column using {@link #notNull()} = true.
 * </p>
 */
@Retention(RUNTIME)
@XmlElement(name="key")
public @interface Key
{
    /**
     * The foreign key column(s). Alternative to using a simple {@link #column()} configuration.
     */
    Column[] columnMapping() default {};

    /**
     * The name of the foreign key column. This may also be specified by nested {@link #columnMapping()} element(s).
     */
    @XmlAttribute String column() default "";
    @XmlAttribute("foreign-key") String foreignKey() default "";

    /**
     * Specifies that the foreign key refers to columns that are not the primary key of the orginal table.
     * (Provided for legacy data.)
     */
    @XmlAttribute("property-ref") String propertyRef() default "";

    /**
     * Specifies whether the foreign key constraint has database-level cascade delete enabled.
     *  We recommend that for systems where delete performance is important, all keys should be
     * defined on-delete="cascade", and Hibernate will use a database-level ON CASCADE DELETE
     * constraint, instead of many individual DELETE statements. Be aware that this feature
     * bypasses Hibernate's usual optimistic locking strategy for versioned data.
     */
    @XmlAttribute OnDeleteSetting onDelete() default OnDeleteSetting.NOACTION;

    /**
     * Specifies that the foreign key columns are not nullable
     * (this is implied whenever the foreign key is also part of the primary key).
     */
    @XmlAttribute("not-null") boolean notNull() default false;

    /**
     * Specifies that the foreign key should have a unique constraint (this is implied whenever the foreign key is also the primary key).
     */
    @XmlAttribute boolean unique() default false;

    /**
     * Specifies that the foreign key should never be updated
     * (this is implied whenever the foreign key is also part of the primary key).
     */
    @XmlAttribute boolean update() default false;

    /**
     * To specify that foreign keys are mapped with ON DELETE CASCADE.
     */
    public static enum OnDeleteSetting
    {
        CASCADE("cascade"),
        NOACTION("noaction");
        private String value;

        OnDeleteSetting(String value)
        {
            this.value = value;
        }

        public String toString()
        {
            return value;
        }
    }

}
